package com.donnees.mysql;


import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JOptionPane;

import com.donnees.dao.AbstractDAO;
import com.metier.logique.ConfigurationProjet;
import com.metier.logique.MSG;


/**
 * @name AbstractDAO
 * @description Objet DAO abstrait parent de tous les DAO, quel que soit la fabrique (MySQL ici uniquement, mais ça marcherait avec d'autres).
 * @method public - abstract - MSG - find - Méthode générique traitant l'objet MSG reçu en paramètre et créant la requête SQL en fonction du MSG.
 * @method public - abstract - MSG - findAll - Méthode récupérant tous les résultats sans filtrage.
 * @method public - MSG - delete - Méthode générique traitant l'objet MSG reçu en paramètre et créant la requête SQL en fonction du MSG.
 * @method public - MSG - update - Méthode générique traitant l'objet MSG reçu en paramètre et créant la requête SQL en fonction du MSG.
 * @method public - MSG - insert - Méthode générique traitant l'objet MSG reçu en paramètre et créant la requête SQL en fonction du MSG.
 * @method protected - MSG - getNombreLignesByResulSet - Retourne le nombre de lignes d'un ResultSet.
 * @method protected - Object[] - generateParameters - Modifie la requetePreparee courante en lui rajoutant des attributs.
 * 
 * @author Ambroise
 * @param <T>
 * @since 07/03/2012
 */
public abstract class MySqlDAO<T> extends AbstractDAO<T>{

	protected String sql;
	protected BigDecimal lastInsertedId;
	protected ResultSet resultats;
	protected PreparedStatement requetePreparee;

	/**
	 * @name delete
	 * @description Méthode delete permettant de faire des requêtes de types DELETE dans la base de données.<br />
	 * 				Les requêtes SQL sont générées automatiquement selon les paramètres fournis.<br />
	 * 				Les requêtes SQL doivent être préparées, il est possible de les faire non préparées mais une protection 
	 * 				des chaînes transmises doit être effectuée auparavant dans ce cas.<br />
	 * 
	 * @param {@link MSG} oMSG
	 * <b>table</b> - String - Obligatoire - Doit contenir le nom de la table où les données seront modifiées.<br />
	 * 			- Ex: UPDATE SET "personne"<br />
	 * 
	 * <b>where</b> - String - Obligatoire - Doit contenir la clause WHERE de la requête SQL.<br />
	 *			- Ex: WHERE "ID_PERSONNE = ?"<br />
	 * 
	 * <b>parametresInconnus</b> - MSG - Obligatoire - Doit contenir autant de valeurs qu'il y a de paramètres dans la requête préparée.<br />
	 * </code><br /><br />
	 * @return {@link MSG} MSG contenant toutes les informations concernant les résultats.<br />
	 * 			"donnees" -> Booléen indiquant si le delete a été fait. (true -> la requête a été exécutée)
	 * 			"typeDonnees" -> "bool"
	 */
	@Override
	public MSG delete(MSG oMSG) {
		oMSG.setData("donnees", false);
		this.sql = "DELETE FROM";
		
		if(oMSG.getData("table") != null && oMSG.getData("where") != null && oMSG.getData("parametresInconnus") != null){
			// Création de la requête SQL.
			this.sql +=  " " + oMSG.getData("table") + " ";
			this.sql += "WHERE " + oMSG.getData("where") + ";";
			
			try{
				Connection oConnection = MySqlDAOFactory.getConnection();
				
				this.requetePreparee = oConnection.prepareStatement(
					this.sql,
					Statement.RETURN_GENERATED_KEYS// On souhaite récupérer les clés, que ce soit le dernier ID inséré ou bien le nombre de lignes modifiées/supprimées.
				);
				
				Object[] parametresInconnus = (Object[]) oMSG.getData("parametresInconnus");
				
				// On appelle la méthode de génération des paramètres.
				generateParameters(parametresInconnus);
				
				
				// On affiche la requête si le projet est en mode debuguage.
				if(ConfigurationProjet.MODE_DEBUG){
					System.out.println(this.requetePreparee);
				}
				
				oMSG.setData("params", this.requetePreparee);
				
				// On exécute la requête.
				this.requetePreparee.executeUpdate();

				oMSG.setData("donnees", true);
				oMSG.setData("typeDonnees", "bool");
				
			}catch (Exception e) {
				JOptionPane.showMessageDialog(null, "Erreur inconnue lors de l'exécution de la méthode "+this.getClass().getSimpleName()+".delete().\n\n<html><body><u>Message d'erreur</u>:<body></html>\n" + e.getMessage(), "Erreur", JOptionPane.ERROR_MESSAGE);
			}
		}else{
			JOptionPane.showMessageDialog(null, "Erreur lors de l'exécution de la méthode "+this.getClass().getSimpleName()+".delete(). Les arguments 'table', 'where' et 'parametresInconnus' sont obligatoires.", "Arguments manquants", JOptionPane.ERROR_MESSAGE);
		}
		return oMSG;
	}

	/**
	 * @name update
	 * @description Méthode update permettant de faire des requêtes de types UPDATE dans la base de données.<br />
	 * 				Les requêtes SQL sont générées automatiquement selon les paramètres fournis.<br />
	 * 				Les requêtes SQL doivent être préparées, il est possible de les faire non préparées mais une protection des chaînes transmises doit être effectuée 
	 * 				auparavant dans ce cas.<br />
	 * 
	 * @param {@link MSG} oMSG
	 * <b>table</b> - String - Obligatoire - Doit contenir le nom de la table où les données seront modifiées.<br />
	 * 			- Ex: UPDATE SET "personne"<br />
	 * 
	 * <b>set</b> - String - Obligatoire - Doit contenir les champs qui vont être modifiés.<br />
	 * 		  	- Ex: SET "PRENOM = ?, DATE_NAISSANCE = ?"<br />
	 * 
	 * <b>where</b> - String - Optionnel - Doit contenir la clause WHERE de la requête SQL.<br />
	 *			- Ex: WHERE "ID_PERSONNE = ?"<br />
	 * 
	 * <b>parametresInconnus</b> - MSG - Obligatoire - Doit contenir autant de valeurs qu'il y a de paramètres dans la requête préparée.<br />
	 * 	</code><br /><br />
	 * @return {@link MSG} MSG contenant toutes les informations concernant les résultats.<br />
	 * 			"donnees" -> Booléen indiquant si l'update a été fait. (true -> la requête a été exécutée)
	 * 			"typeDonnees" -> "bool"
	 */
	@Override
	public MSG update(MSG oMSG) {
		oMSG.setData("donnees", false);
		this.sql = "UPDATE";
		
		if(oMSG.getData("table") != null && oMSG.getData("set") != null && oMSG.getData("where") != null && oMSG.getData("parametresInconnus") != null){
			// Création de la requête SQL.
			this.sql +=  " " + oMSG.getData("table") + " ";
			this.sql += "SET " + oMSG.getData("set") + " ";
			this.sql += "WHERE " + oMSG.getData("where") + ";";
			
			try{
				Connection oConnection = MySqlDAOFactory.getConnection();
				
				this.requetePreparee = oConnection.prepareStatement(
					this.sql,
					Statement.RETURN_GENERATED_KEYS// On souhaite récupérer les clés, que ce soit le dernier ID inséré ou bien le nombre de lignes modifiées/supprimées.
				);
				
				Object[] parametresInconnus = (Object[]) oMSG.getData("parametresInconnus");
				
				// On appelle la méthode de génération des paramètres.
				generateParameters(parametresInconnus);
				
				
				// On affiche la requête si le projet est en mode debuguage.
				if(ConfigurationProjet.MODE_DEBUG){
					System.out.println(this.requetePreparee);
				}
				
				oMSG.setData("params", this.requetePreparee);
				
				// On exécute la requête.
				this.requetePreparee.executeUpdate();
				oMSG.setData("donnees", true);
				oMSG.setData("typeDonnees", "bool");
				
			}catch (Exception e) {
				JOptionPane.showMessageDialog(null, "Erreur inconnue lors de l'exécution de la méthode "+this.getClass().getSimpleName()+".update().\n\n<html><body><u>Message d'erreur</u>:<body></html>\n" + e.getMessage(), "Erreur", JOptionPane.ERROR_MESSAGE);
			}
		}else{
			JOptionPane.showMessageDialog(null, "Erreur lors de l'exécution de la méthode "+this.getClass().getSimpleName()+".update(). Les arguments 'table', 'set', 'where' et 'parametresInconnus' sont obligatoires.", "Arguments manquants", JOptionPane.ERROR_MESSAGE);
		}
		return oMSG;
	}

	/**
	 * @name insert
	 * @description Méthode insert permettant de faire des requêtes de types INSERT dans la base de données.<br />
	 * 				Les requêtes SQL sont générées automatiquement selon les paramètres fournis.<br />
	 * 				Les requêtes SQL doivent être préparées, il est possible de les faire non préparées 
	 * 				mais une protection des chaînes transmises doit être effectuée auparavant dans ce cas.<br />
	 * 
	 * @param  {@link MSG} oMSG <br />
	 * <b>table</b> - String - Obligatoire - Doit contenir le nom de la table où les données seront insérées.<br />
	 * 			- Ex: INSERT INTO "personne"<br />
	 * 
	 * <b>champs</b> - String - Obligatoire - Doit contenir les champs qui vont recevoir une donnée.<br />
	 * 		  	- Ex: "NOM, PRENOM, DATE_NAISSANCE, EMAIl, TEL_FIXE, TEL_PORTABLE, MSN, TWITTER, FACEBOOK, GOOGLE, INFORMATIONS, TYPE_PERSONNE, TYPE_RENCONTRE"<br />
	 * 
	 * <b>values</b> - String - Obligatoire - Doit contenir les valeurs (requête simple) ou des "?" pour chaque valeur à insérer (Requête préparée).<br />
	 * 			- Ex: Requête préparée: "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?"<br />
	 * 
	 * <b>parametresInconnus</b> - MSG - Obligatoire - Doit contenir autant de valeurs qu'il y a de paramètres dans la requête préparée.<br />
	 * 	
	 * 	</code><br /><br />
	 * @return {@link MSG} MSG contenant toutes les informations concernant les résultats.<br />
	 * 			"donnees" -> Liste de BigDecimal correspondants aux IDs insérés. Ou BigDecimal correspondant à l'ID inséré.
	 * 			"typeDonnees" -> Type de données retournée: List ("List"), BigDecimal ("BigDecimal") ou null ("0")
	 */
	@Override
	public MSG insert(MSG oMSG) {
		this.sql = "INSERT INTO";
		
		if(oMSG.getData("table") != null && oMSG.getData("champs") != null && oMSG.getData("values") != null && oMSG.getData("parametresInconnus") != null){
			// Création de la requête SQL.
			this.sql +=  " " + oMSG.getData("table") + " ";
			this.sql += "(" + oMSG.getData("champs") + ")";
			this.sql += " VALUES (" + oMSG.getData("values") + ");";
			
			try{
				Connection oConnection = MySqlDAOFactory.getConnection();
				
				this.requetePreparee = oConnection.prepareStatement(
					this.sql,
					Statement.RETURN_GENERATED_KEYS// On souhaite récupérer les clés, que ce soit le dernier ID inséré ou bien le nombre de lignes modifiées/supprimées.
				);
				
				Object[] parametresInconnus = (Object[]) oMSG.getData("parametresInconnus");
				
				// On appelle la méthode de génération des paramètres.
				generateParameters(parametresInconnus);
				
				
				// On affiche la requête si le projet est en mode debuguage.
				if(ConfigurationProjet.MODE_DEBUG){
					System.out.println(this.requetePreparee);
				}
				
				oMSG.setData("params", this.requetePreparee);
				
				// On exécute la requête.
				this.requetePreparee.executeUpdate();
				this.resultats = this.requetePreparee.getGeneratedKeys();
				
				// On compte le nombre de résultats.
				int nbResultats = getNombreLignesByResulSet(this.resultats);
				this.resultats.first();
				// S'il y a eu plusieurs modifications on retourne une liste d'id.
				if(nbResultats > 1){
					List<BigDecimal> listeId = new ArrayList<BigDecimal>();
					
					while(resultats.next()){
						listeId.add(resultats.getBigDecimal(1));
					}
					
					// On stocke la liste des ID générés dans le MSG.
					oMSG.setData("donnees", listeId);
					oMSG.setData("typeDonnees", "List");
					
				}else if(nbResultats == 1){
					// Sinon s'il n'y a qu'un seul résultat on retourne un BigDecimal.
					BigDecimal idGenerated = resultats.getBigDecimal(1);
					
					// On stocke l'ID généré dans le MSG.
					oMSG.setData("donnees", idGenerated);
					oMSG.setData("typeDonnees", "BigDecimal");
				}else{
					// On dit qu'il y a pas de résultat dans le MSG de retour.
					oMSG.setData("donnees", 0);
					oMSG.setData("typeDonnees", "null");
				}
						
			}catch (Exception e) {
				JOptionPane.showMessageDialog(null, "Erreur inconnue lors de l'exécution de la méthode "+this.getClass().getSimpleName()+".insert().\n\n<html><body><u>Message d'erreur</u>:<body></html>\n" + e.getMessage(), "Erreur", JOptionPane.ERROR_MESSAGE);
			}
		}else{
			JOptionPane.showMessageDialog(null, "Erreur lors de l'exécution de la méthode "+this.getClass().getSimpleName()+".insert(). Les arguments 'table', 'champs', 'values' et 'parametresInconnus' sont obligatoires.", "Arguments manquants", JOptionPane.ERROR_MESSAGE);
		}
		return oMSG;
	}
	
	/**
	 * @name getNombreLignesByResulSet
	 * @description Retourne le nombre de lignes d'un ResultSet.
	 * @since 28/02/2012
	 */
	protected int getNombreLignesByResulSet(ResultSet resultSet){
		try {
			resultSet.last();
			int nombreLignes = resultSet.getRow();
			
			// On replace le curseur à la première position.
			resultSet.beforeFirst();
			
			return nombreLignes;
		} catch (SQLException e) {
			if(ConfigurationProjet.MODE_DEBUG){
				System.out.println("Exception SQL dans getNombreLigne:\n\n"+e.getMessage());
				e.printStackTrace();
			}
			return -1;
		}
	}
	
	/**
	 * @name generateParameters
	 * @description: Modifie la requetePreparee courante en lui rajoutant des attributs.
	 * @param Object[] parametresInconnus - Tableau d'Objects - Contient un tableau d'objets indexés numériquement.<br />
	 * 			Ex:<br /><br /><code>
				    Object[] parametresInconnus = new Object[13];
					parametresInconnus[0] = "Dhenain";
					parametresInconnus[1] = "Ambroise";
					try {
						parametresInconnus[2] = GestionnaireDate.getJavaSqlDateByDate(new SimpleDateFormat("yyyy/MM/dd").parse("1990/04/06"));
					} catch (ParseException e) {
						parametresInconnus[2] = new java.sql.Date(0L);
					}
					parametresInconnus[3] = "";
					parametresInconnus[4] = "";
					parametresInconnus[5] = "";
					parametresInconnus[6] = "";
					parametresInconnus[7] = "";
					parametresInconnus[8] = "";
					parametresInconnus[9] = "";
					parametresInconnus[10] = "";
					parametresInconnus[11] = 0;
					parametresInconnus[12] = 0;
	 * </code>
	 */
	protected void generateParameters(Object[] parametresInconnus){
		try{
			int i = 1;// Compteur
			// Pour chaque case, en fonction du type de la variable on prépare la ligne différemment.
			for (Object value : parametresInconnus) {
				if(value.getClass() == String.class){
					this.requetePreparee.setString(i, (String)value);
				}else if(value.getClass() == Integer.class){
					this.requetePreparee.setInt(i, (Integer)value);
				}else if(value.getClass() == Boolean.class){
					this.requetePreparee.setBoolean(i, (Boolean)value);
				}else if(value.getClass() == java.sql.Date.class){
					this.requetePreparee.setDate(i, (java.sql.Date)value);
				}else if(value.getClass() == BigDecimal.class){
					this.requetePreparee.setBigDecimal(i, (BigDecimal)value);
				}else if(value.getClass() == Float.class){
					this.requetePreparee.setFloat(i, (Float)value);
				}else if(value.getClass() == Double.class){
					this.requetePreparee.setDouble(i, (Double)value);
				}else if(value.getClass() == Short.class){
					this.requetePreparee.setShort(i, (Short)value);
				}else if(value.getClass() == Byte.class){
					this.requetePreparee.setByte(i, (Byte)value);
				}else if(value.getClass() == Long.class){
					this.requetePreparee.setLong(i, (Long)value);
				}else if(value.getClass() == Character.class){// On convertit un Character en String - Non testé !
					this.requetePreparee.setString(i, (String)value);
				}else{
					JOptionPane.showMessageDialog(null, "Alerte, cas non prévu lors de la méthode generateParameters(), le type fourni n'est pas géré lors de la génération de la requête préparée !", "Alerte !", JOptionPane.WARNING_MESSAGE);
				}
				
				i++;
			}
		}catch (Exception e) {
			JOptionPane.showMessageDialog(null, "Une erreur est survenue lors de l'exécution de la méthode generateParameters().\n\n<html><body><u>Message d'erreur</u>:<body></html>\n" + e.getMessage(), "Erreur inconnue !", JOptionPane.ERROR_MESSAGE);
		}
	}
}
